easylua = {} local s = easylua

local function compare(a, b)

	if a == b then return true end
	if a:find(b, nil, true) then return true end
	if a:lower() == b:lower() then return true end
	if a:lower():find(b:lower(), nil, true) then return true end

	return false
end

local function comparenick(a, b)
	if not GLib then return compare (a, b) end
	
	if a == b then return true end
	if a:lower() == b:lower() then return true end
	if GLib.UTF8.MatchTransliteration(a, b) then return true end

	return false
end

local function compareentity(ent, str)
	if ent.GetName and compare(ent:GetName(), str) then
		return true
	end

	if ent:GetModel() and compare(ent:GetModel(), str) then
		return true
	end

	return false
end

if CLIENT then
	function easylua.PrintOnServer(...)
		local args = {...}
		local new = {}

		for key, value in pairs(args) do
			table.insert(new, luadata.ToString(value) or tostring(value))
		end

		RunConsoleCommand("easylua_print", unpack(new))
	end
end

function easylua.Print(...)
	if CLIENT then
		easylua.PrintOnServer(...)
	end
	if SERVER then
		local args = {...}
		local str = ""

		Msg(string.format("[EasyLua %s] ", IsValid(me) and me:Nick() or "Sv"))

		for key, value in pairs(args) do
			str = str .. type(value) == "string" and value or luadata.ToString(value) or tostring(value)

			if key ~= #args then
				str = str .. ","
			end
		end

		print(str)
	end
end

if SERVER then
	function easylua.CMDPrint(ply, cmd, args)
		args = table.concat(args, ", ")

		Msg(string.format("[EasyLua %s] ", IsValid(ply) and ply:Nick() or "Sv"))
		print(args)
	end

	concommand.Add("easylua_print", easylua.CMDPrint)
end

function easylua.FindEntity(str)
	if not str then return NULL end

	str = tostring(str)

	if str == "#this" and IsEntity(this) and this:IsValid() then
		return this
	end

	if str == "#me" and IsEntity(me) and me:IsPlayer() then
		return me
	end

	if str == "#all" then
		return all
	end

	if str:sub(1,1) == "#" then
		local str = str:sub(2)

		if #str > 0 then
			str = str:lower()
			local found
			for key, data in pairs(team.GetAllTeams()) do
				if data.Name:lower() == str then
					found = data.Name:lower()
					print("found!!!")
					break
				end
			end

			if not found then
				local classes = {}

				for key, ent in pairs(ents.GetAll()) do
					classes[ent:GetClass():lower()] = true
				end

				for class in pairs(classes) do
					if class:lower() == str then
						print("found", class)
						found = class
					end
				end
			end

			if found then
				local func = CreateAllFuncton(function(v) return v:GetClass():lower() == class end)
				print(func:GetName())
				return func
			end
		end
	end

	-- unique id
	local ply = player.GetByUniqueID(str)
	if ply and ply:IsPlayer() then
		return ply
	end

	-- steam id
	if str:find("STEAM") then
		for key, _ply in pairs(player.GetAll()) do
			if _ply:SteamID() == str then
				return _ply
			end
		end
	end

	if str:sub(1,1) == "_" and tonumber(str:sub(2)) then
		str = str:sub(2)
	end

	if tonumber(str) then
		ply = Entity(tonumber(str))
		if ply:IsValid() then
			return ply
		end
	end

	-- community id
	if #str == 17 then

	end

	-- ip
	if SERVER then
		if str:find("%d+%.%d+%.%d+%.%d+") then
			for key, _ply in pairs(player.GetAll()) do
				if _ply:IPAddress():find(str) then
					return _ply
				end
			end
		end
	end

	-- Search again with colorcode stripped
	for key, ply in pairs(player.GetAll()) do
		if comparenick(ply:Nick(), str) then
			return ply
		end
		if comparenick(ply:Nick():gsub("%^%d", ""), str) then
			return ply
		end
	end

	for key, ent in pairs(ents.GetAll()) do
		if compareentity(ent, str) then
			return ent
		end
	end

	do -- class

		local _str, idx = str:match("(.-)(%d+)")
		if idx then
			idx = tonumber(idx)
			str = _str
		else
			str = str
			idx = (me and me.easylua_iterator) or 0
		end

		local found = {}

		for key, ent in pairs(ents.GetAll()) do
			if compare(ent:GetClass(), str) then
				table.insert(found, ent)
			end
		end

		return found[math.Clamp(idx%#found, 1, #found)] or NULL
	end
end

function easylua.CreateEntity(class)
	local mdl = "error.mdl"

	if IsEntity(class) and class:IsValid() then
		this = class
	elseif class:find(".mdl", nil, true) then
		mdl = class
		class = "prop_physics"

		this = ents.Create(class)
		this:SetModel(mdl)
	else
		this = ents.Create(class)
	end

	this:Spawn()
	this:SetPos(there + Vector(0,0,this:BoundingRadius() * 2))
	this:DropToFloor()
	this:PhysWake()

	undo.Create(class)
		undo.SetPlayer(me)
		undo.AddEntity(this)
	undo.Finish()
	
	return this
end

function easylua.CopyToClipboard(var, ply)
	ply = ply or me
	if luadata then
		local str = luadata.ToString(var)

		if not str and IsEntity(var) and var:IsValid() then
			if var:IsPlayer() then
				str = string.format("player.GetByUniqueID(--[[%s]] %q)", var:GetName(), var:UniqueID())
			else
				str = string.format("Entity(%i)", var:EntIndex())
			end

		end

		if CLIENT then
			SetClipboardText(str)
		end

		if SERVER then
			local str = string.format("SetClipboardText(%q)", str)
			if #str > 255 then
				if luadev and luadev.RunOnClient then
					luadev.RunOnClient(str, ply)
				else
					error("Text too long to send and luadev not found",1)
				end
			else
				ply:SendLua(str)
			end
		end
	end
end


function easylua.Start(ply)
	ply = ply or CLIENT and LocalPlayer() or nil

	if not ply or not IsValid(ply) then return end

	local vars = {}
		local trace = util.QuickTrace(ply:EyePos(), ply:GetAimVector() * 10000, {ply, ply:GetVehicle()})

		if trace.Entity:IsWorld() then
			trace.Entity = NULL
		end

		vars.me = ply
		vars.this = trace.Entity
		vars.wep = ply:GetActiveWeapon()

		vars.there = trace.HitPos
		vars.here = trace.StartPos
		vars.dir = ply:GetAimVector()

		vars.trace = trace
		vars.length = trace.StartPos:Distance(trace.HitPos)

		vars.copy = s.CopyToClipboard
		vars.create = s.CreateEntity
		vars.prints = s.PrintOnServer

		if vars.this:IsValid() then
			vars.phys = vars.this:GetPhysicsObject()
			vars.model = vars.this:GetModel()
		end

		vars.E = s.FindEntity
		vars.last = ply.easylua_lastvars

		s.vars = vars
		local old_G={}
		s.oldvars=old_G
		
	for k,v in pairs(vars) do old_G[k]=_G[k] _G[k] = v end

	ply.easylua_lastvars = vars
	ply.easylua_iterator = (ply.easylua_iterator or 0) + 1
end

function easylua.End()
	if s.vars then
		for key, value in pairs(s.vars) do
			if s.oldvars and s.oldvars[key] then
				_G[key] = s.oldvars[key]
			else 
				_G[key] = nil
			end
		end
	end
end

do -- env meta
	local META = {}

	local _G = _G
	local easylua = easylua
	local tonumber = tonumber
	
	local nils={
		["CLIENT"]=true,
		["SERVER"]=true,
	}
	function META:__index(key)
		local var = _G[key]

		if var ~= nil then
			return var
		end

		if not nils [key] then -- uh oh
			var = easylua.FindEntity(key)
			if var:IsValid() then
				return var
			end
		end

		return nil
	end

	function META:__newindex(key, value)
		_G[key] = value
	end

	easylua.EnvMeta = setmetatable({}, META)
end

function easylua.RunLua(ply, code, env_name)
	local data =
	{
		error = false,
		args = {},
	}

	easylua.Start(ply)
		local header = ""

		for key, value in pairs(s.vars) do
			header = header .. string.format("local %s = %s ", key, key)
		end

		code = header .. "; " .. code

		env_name = env_name or string.format("%s", tostring(
			IsValid(ply) and ply:IsPlayer()
				and	"["..ply:SteamID():gsub("STEAM_","").."]"..ply:Name()
				or ply))

		data.env_name = env_name

		local func = CompileString(code, env_name, false)

		if type(func) == "function" then
			setfenv(func, easylua.EnvMeta)

			local args = {pcall(func)}

			if args[1] == false then
				data.error = args[2]
			end

			table.remove(args, 1)
			data.args = args
		else
			data.error = func
		end
	easylua.End()

	return data
end

function easylua.StartWeapon(classname)
	_G.SWEP = {Primary = {}, Secondary = {}}

	SWEP.Base = "weapon_cs_base"

	SWEP.ClassName = classname or "no_swep_name_" .. me:Nick() .. "_" .. me:UniqueID()
end

function easylua.EndWeapon(spawn, reinit)
	weapons.Register(SWEP, SWEP.ClassName, true)

	for key, entity in pairs(ents.FindByClass(SWEP.ClassName)) do
		if entity:GetTable() then table.Merge(entity:GetTable(), SWEP) end
		if reinit then
			entity:Initialize()
		end
	end

	if SERVER and spawn then
		SafeRemoveEntity(me:GetWeapon(SWEP.ClassName))
		local me = me
		local class = SWEP.ClassName
		timer.Simple(0.2, function() if me:IsPlayer() then me:Give(class) end end)
	end

	SWEP = nil
end

function easylua.StartEntity(classname)
	_G.ENT = {}

	ENT.Type = "anim"
	ENT.Base = "base_anim"

	ENT.Model = Model("models/props_borealis/bluebarrel001.mdl")
	ENT.ClassName = classname or "no_ent_name_" .. me:Nick() .. "_" .. me:UniqueID()
end

function easylua.EndEntity(spawn, reinit)
	scripted_ents.Register(ENT, ENT.ClassName, true)

	for key, entity in pairs(ents.FindByClass(ENT.ClassName)) do
		table.Merge(entity:GetTable(), ENT)
		if reinit then
			entity:Initialize()
		end
	end

	if SERVER and spawn then
		create(ENT.ClassName)
	end

	ENT = nil
end

do -- all
	local META = {}

	function META:__index(key)
		return function(_, ...)
			local args = {}

			for _, ent in pairs(ents.GetAll()) do
				if (not self.func or self.func(ent)) then
					if type(ent[key]) == "function" or ent[key] == "table" and type(ent[key].__call) == "function" and getmetatable(ent[key]) then
						table.insert(args, {ent = ent, args = (ent[key](ent, ...))})
					else
						ErrorNoHalt("attempt to call field '" .. key .. "' on ".. tostring(ent) .." a " .. type(ent[key]) .. " value\n")
					end
				end
			end

			return args
		end
	end

	function META:__newindex(key, value)
		for _, ent in pairs(ents.GetAll()) do
			if not self.func or self.func(ent) then
				ent[key] = value
			end
		end
	end
	
	function CreateAllFuncton(func)
		return setmetatable({func = func}, META)
	end

	all = CreateAllFuncton(function(v) return v:IsPlayer() end)
	props = CreateAllFuncton(function(v) return v:GetClass() == "prop_physics" end)
	props = CreateAllFuncton(function(v) return util.IsValidPhysicsObject(vm) end)
	bots = CreateAllFuncton(function(v) return v:IsBot() end)
	these = CreateAllFuncton(function(v) return easylua and table.HasValue(constraint.GetAllConstrainedEntities(this), v) end)
end